package com.xnx3.j2ee.util;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Component;
import com.xnx3.Lang;
import com.xnx3.StringUtil;
import com.xnx3.elasticsearch.jsonFormat.JsonFormatInterface;
import com.xnx3.j2ee.Global;
import com.xnx3.j2ee.vo.ActionLogListVO;
import com.xnx3.j2ee.vo.ElasticSearchPageListVO;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
 * ElasticSearch 工具类。使用此工具类的前提是 application.properties 中配置好了相关参数
 * @author 管雷鸣
 */
@Component
public class ElasticSearchUtil{
	private static com.xnx3.elasticsearch.ElasticSearchUtil es;
	private static boolean starting = true;	//当前状态是否是在启动中，如果是在启动中（tomcat启动可能需要半分钟），那此处为true，如果tomcat已启动完成加载完了（也就是ElasticSearch 的参数并已经初始化完成），那此处为false
	
	static {
		new Thread(new Runnable() {
			public void run() {
				
				//当数据库加载完后，再初始化
				while(Global.system.size() < 1){
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				//初始化es配置参数
				String hostname = ApplicationPropertiesUtil.getProperty("wm.elasticsearch.hostname");
				int port = Lang.stringToInt(ApplicationPropertiesUtil.getProperty("wm.elasticsearch.port"), 9200);
				String scheme = ApplicationPropertiesUtil.getProperty("wm.elasticsearch.scheme");
				String username = ApplicationPropertiesUtil.getProperty("wm.elasticsearch.username");
				String password = ApplicationPropertiesUtil.getProperty("wm.elasticsearch.password");
				
				//判断是否使用es进行日志记录，条件便是 hostname 是否为空。若为空，则不使用
				if(hostname != null && hostname.length() > 0){
					//使用
					es = new com.xnx3.elasticsearch.ElasticSearchUtil(hostname, port, scheme);
					if((username != null && username.length() > 0) && (password != null && password.length() > 0)) {
						//使用账号密码模式
						es.setUsernameAndPassword(username, password);
					}
					ConsoleUtil.info("开启ElasticSearch进行操作记录");
					
					//重写序列化接口
					es.setJsonFormatInterface(new JsonFormatInterface() {
						@Override
						public String mapToJsonString(Map<String, Object> params) {
							if(params == null){
								params = new HashMap<String, Object>();
							}
							return JSONObject.fromObject(params).toString();
						}
					});
				}
				
				starting = false;
			}
		}).start();
	}
	
	/**
	 * 获取 {@link com.xnx3.elasticsearch.ElasticSearchUtil} 对象
	 * @return application.properties 中 wm.elasticsearch 相关参数所配置初始化的{@link com.xnx3.elasticsearch.ElasticSearchUtil}对象。拿到可直接使用。
	 */
	public static com.xnx3.elasticsearch.ElasticSearchUtil getElasticSearch(){
		if(isStarting()) {
			ConsoleUtil.debug("elasticsearch 尚未初始化，请控制在项目启动成功后再调用此");
			return null;
		}
		if(!isUsed()) {
			ConsoleUtil.debug("elasticsearch 未使用。若要使用，可配置 application.properties 中 wm.elasticsearch 相关参数");
			return null;
		}
		return es;
	}
	
	/**
	 * 是否使用 ElasticSearch （application.properties中是否已配置es的参数）
	 * @return true:使用
	 */
	public static boolean isUsed() {
		if(es == null){
			return false;
		}
		return true;
	}
	
	/**
	 * 当前状态是否是在启动中，如果是在启动中（tomcat启动可能需要半分钟），那此处为true，如果tomcat已启动完成加载完了（也就是ElasticSearch 的参数并已经初始化完成），那此处为false。
	 * <p>因为如果是在启动中，那</p>
	 * @return true:使用
	 */
	public static boolean isStarting() {
		return starting;
	}
	
	/**
	 * 项目启动时，检查创建索引是否存在，如果没有创建，那么自动创建这个索引。
	 * <p>注意，使用此创建时，会自动创建一个线程Thread，监测 elasticsearch 的初始化，当初始化后才会进行创建。一个项目中，不建议要超过20个使用此方法进行监测创建的 indexName</p>
	 * <p>正常创建索引，</p>
	 * @param indexName 要创建的索引名字，传入如 userlog
	 */
	public static void projectStartCheckCreateIndex(String indexName) {
		new Thread(new Runnable() {
			public void run() {
				while(isStarting()) {
					try {
						Thread.sleep(300);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				//如果不使用es，直接退出
				if(!isUsed()) {
					return;
				}
				
				//判断index是否创建了，如果没创建，进行创建
				if(!es.existIndex(indexName)) {
					ConsoleUtil.info("自动创建 ElasticSearch 的 index : "+indexName);
					try {
						es.createIndex(indexName);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				
			}
		}).start();
		
	}

	
	/**
	 * 将日志查询出来，以列表+分页的数据输出
	 * <p>获取到的数据排序规则：这里是按照数据创建时间（time字段），倒序排列，时间越往后的，显示越靠前。当前，前提是你写入数据的时候得有 time 这个字段</p>
	 * @param indexName 索引名字
	 * @param query 查询条件，传入如： name:guanleiming AND age:123
	 * @param everyPageNumber 每页显示几条，最大100
	 * @param request {@link HttpServletRequest} 分页会使用到这个
	 * @return 如果失败， vo.getResult() == ActionLogListVO.FAILURE
	 */
	public static ElasticSearchPageListVO list(String indexName, String query,int everyPageNumber, HttpServletRequest request) {
		return list(indexName, query, everyPageNumber, SortBuilders.fieldSort("time").order(SortOrder.DESC), request);
	}
	
	/**
	 * 将日志查询出来，以列表+分页的数据输出
	 * @param indexName 索引名字
	 * @param query 查询条件，传入如： name:guanleiming AND age:123
	 * @param everyPageNumber 每页显示几条，最大100
	 * @param sort 排序方式。比如按照time字段进行倒序排序，则传入如: 
	 * 	<pre>
	 * 	SortBuilders.fieldSort("time").order(SortOrder.DESC)
	 * 	</pre>
	 * @param request {@link HttpServletRequest} 分页会使用到这个
	 * @return 如果失败， vo.getResult() == ActionLogListVO.FAILURE
	 */
	public static ElasticSearchPageListVO list(String indexName, String query,int everyPageNumber, SortBuilder sort, HttpServletRequest request) {
		ElasticSearchPageListVO vo = new ElasticSearchPageListVO();
		
		//统计符合条件的总数量
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
	    CountRequest countRequest = new CountRequest();
	    //构造查询条件
    	if(query != null && query.length() > 0){
    		//有查询条件，才会进行查询，否则会查出所有
    		QueryBuilder queryBuilder = QueryBuilders.queryStringQuery(query);
    		searchSourceBuilder.query(queryBuilder);
    	}
	    countRequest.indices(indexName).source(searchSourceBuilder);
	    long count = 0;
	    CountResponse countResponse = null;
	    try {
	        countResponse = getElasticSearch().getRestHighLevelClient().count(countRequest, RequestOptions.DEFAULT);
	        count = countResponse.getCount();
	    } catch (IOException e) {
	    	e.printStackTrace();
	    }
	    Page page = new Page((int) count, everyPageNumber, request);
	    
	    //limit查询条数
	    int limitNumber = everyPageNumber;
	    //判断当前是否是最后一页
	    if(page.isCurrentLastPage()){
	    	//最后一页，那limit条数肯定不满足一页多少条的标准了，肯定是要少的，那就计算出当前一共几条，就显示几条
	    	limitNumber = (int) (count - page.getLimitStart() );
	    }
	    
	    long max_result_window = 10000;	//默认就是10000
	    try {
	    	Response res = getElasticSearch().getRestClient().performRequest(new Request("GET", "/"+indexName+"/_settings"));
	    	String text = StringUtil.inputStreamToString(res.getEntity().getContent(), "UTF-8");
	    	JSONObject setJson = JSONObject.fromObject(text);
	    	JSONObject indexJson = setJson.getJSONObject(indexName).getJSONObject("settings").getJSONObject("index");
	    	if(indexJson.get("max_result_window") != null){
	    		//自己单独设置过 max_result_window
	    		max_result_window = indexJson.getLong("max_result_window");
	    	}
		} catch (Exception e) {
			e.printStackTrace();
		}
	    
	    //判断最大显示条数是否超过可显示的最大条数
	    if((page.getLimitStart() + limitNumber) > max_result_window){
	    	vo.setBaseVO(ActionLogListVO.FAILURE, "显示最大条数超过系统预设优化的最大条数"+max_result_window+"条。");
	    	ConsoleUtil.log("显示最大条数超过系统预设优化的最大条数"+max_result_window+"条。你可以设置ElasticSearch中，"+indexName+"索引的max_result_window属性来设置更大条数。");
	    	return vo;
	    }
	    
	    //获取查询结果的数据 
		List<Map<String, Object>> list = getElasticSearch().search(indexName, query, page.getLimitStart(), limitNumber, SortBuilders.fieldSort("time").order(SortOrder.DESC));
		for (int i = 0; i < list.size(); i++) {
			Map<String, Object> map = list.get(i);
			
			//兼容以前的阿里云日志服务的版本
			Object timeObj = map.get("time");
			if(timeObj == null){
				map.put("logtime", 0l);
			}else{
				map.put("logtime", timeObj);
			}
		}
		vo.setList(list);
		vo.setPage(page);
		
		return vo;
	}
	
}
